﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;
using Tomahawk.Runtime.Player;
using Tomahawk.Runtime.Input;
using Tomahawk.Runtime.Canvas;

namespace Tomahawk.Runtime.Logic.UI
{

    /// <summary>
    /// An UIScene represents an UI canvas, that occupies the whole screen.
    /// </summary>
    public class XUIScene : XUIControl
    {

        #region "Serializable Attributes"

        [XmlAttribute("layer")]
        public CanvasManager.CanvasLayers CanvasLayer = CanvasManager.CanvasLayers.Canvas1;

        [XmlElement("Timeline")]
        public Timeline.Timeline Timeline = new Timeline.Timeline();
        #endregion

        /// <summary>
        /// Constructor logic
        /// </summary>
        public XUIScene()
        {
            controlDictionary = new Dictionary<string, XUIControl>();

            //default input management configuration
            InputConfig.Enabled = false;
            InputConfig.Keyboard = false;
            InputConfig.Player = PlayerInputFilter.None;

            //list all gamepads to process
            gamePadList = new PlayerGamePad[4];
        }

        #region "Control Indexing"

        /// <summary>
        /// Indexes all the UIScene controls by its name
        /// </summary>
        private Dictionary<string, XUIControl> controlDictionary;

        /// <summary>
        /// Traverse all child controls of the given control, and add them all to the dictonary index
        /// </summary>
        private void indexChildControls(XUIControl startControl)
        {
            //Index the controls that has a name assigned
            foreach (XUIControl control in startControl.ChildControls)
            {
                //if the control has UIName
                if ((control.UIName != null) && (control.UIName != ""))
                {
                    //add to dictionary
                    addToControlDictionary(control);
                }
            }

            //Index the controls that has no name assigned
            foreach (XUIControl control in startControl.ChildControls)
            {
                //if the control has no UIName
                if ((control.UIName == null) || (control.UIName == ""))
                {
                    //autoassing a name
                    this.autogenerateName(control);

                    //add to dictionary
                    this.addToControlDictionary(control);
                }
            }
        }

        /// <summary>
        /// Add a control to the name dictionary. Controls the possible error situations.
        /// Then try to add all of it child controls.
        /// </summary>
        private void addToControlDictionary(XUIControl control)
        {
            try
            {
                //if nonamed, autogenerate
                if ((control.UIName == null) || (control.UIName == ""))
                    this.autogenerateName(control);

                //add the control to the dictionary
                controlDictionary.Add(control.UIName, control);

                //add its child control recursivelly
                foreach (XUIControl childcontrol in control.ChildControls)
                {
                    //if nonamed, autogenerate
                    if ((childcontrol.UIName == null) || (childcontrol.UIName == ""))
                        this.autogenerateName(childcontrol);

                    //add to dictionary
                    controlDictionary.Add(childcontrol.UIName, childcontrol);
                    this.indexChildControls(childcontrol);
                }
            }
            catch (ArgumentException)
            {
                throw new DuplicatedUIName(this, control);
            }
        }

        /// <summary>
        /// Remove a contol from the dictionary
        /// </summary>
        private void removeFromControlDictionary(XUIControl control)
        {
            controlDictionary.Remove(control.UIName);
        }

        /// <summary>
        /// Utility counter for autogerated names
        /// </summary>
        static int autogeneratedNameCounter = 0;

        /// <summary>
        /// Asing an autogenerate name to the control
        /// </summary>
        internal void autogenerateName(XUIObject control)
        {
            bool found = false;
            string proposedName = "";
            while (!found)
            {
                proposedName = String.Format("{0}_{1}",
                    control.GetType().Name, autogeneratedNameCounter.ToString("000"));
                if (!this.controlDictionary.Keys.Contains(proposedName)) found = true;
                autogeneratedNameCounter++;
            }
            //assign the name found
            control.UIName = proposedName;
        }

        /// <summary>
        /// Check navegation references by name on all placeable controls recursivelly
        /// </summary>
        internal void checkFocusRenferences(XUIControl control)
        {
            //call the method recursivelly
            foreach (XUIControl child in control.ChildControls)
            {
                checkFocusRenferences(child);
            }

            //if the control is a placeable
            XUIPlaceableControl placeable = control as XUIPlaceableControl;
            if (placeable != null)
            {
                // DOWN REFERENCE
                if ((placeable.FocusReferences.Down != null) && (placeable.FocusReferences.Down != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Down, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Down, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Down = "";
                    }
                }

                // UP REFERENCE
                if ((placeable.FocusReferences.Up != null) && (placeable.FocusReferences.Up != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Up, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Up, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Up = "";
                    }
                }

                // LEFT REFERENCE
                if ((placeable.FocusReferences.Left != null) && (placeable.FocusReferences.Left != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Left, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Left, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Left = "";
                    }
                }

                // RIGHT REFERENCE
                if ((placeable.FocusReferences.Right != null) && (placeable.FocusReferences.Right != ""))
                {
                    XUIControl outControl = null;
                    if (!this.controlDictionary.TryGetValue(placeable.FocusReferences.Right, out outControl))
                    {
                        //warn the error
                        string error = String.Format("Control '{0}' not found, on focus references in control '{1}', on scene '{2}'",
                            placeable.FocusReferences.Right, placeable.UIName, this.UIName);
                        Engine.Instance.DebugConsole.Warn(error);
                        //leave the reference empty
                        placeable.FocusReferences.Right = "";
                    }
                }
            }
        }

        /// <summary>
        /// Get a control from the dictionary
        /// </summary>
        public XUIControl GetControlByName(string controlName)
        {
            XUIControl retVal = null;
            this.controlDictionary.TryGetValue(controlName, out retVal);
            if (retVal == null)
            {
                Engine.Instance.DebugConsole.Warn("Control not found on UIScene: " + controlName);
            }
            return retVal;
        }

        protected override void OnControlAddded(XUIControl control)
        {
            base.OnControlAddded(control);

            //add to the dictionary
            this.addToControlDictionary(control);
        }

        protected override void OnControlRemoved(XUIControl control)
        {
            base.OnControlRemoved(control);

            //remove from the dictionary
            this.removeFromControlDictionary(control);
        }

        public void IndexControl(XUIControl control)
        {
            this.addToControlDictionary(control);
        }

        #endregion

        /// <summary>
        /// Initialization
        /// </summary>
        protected override void OnInitialize()
        {
            base.OnInitialize();

            //note: at this point all child controls are initialized

            //index all child controls in the dictionary
            this.indexChildControls(this);

            //check for references by name on focus references
            this.checkFocusRenferences(this);

            //this logic object requires updates
            this.UpdateRequired = true;

            //prepare for keypad input
            if (InputConfig.Player != PlayerInputFilter.None)
                this.initializeKeypadInput();

        }

        private Object FindTargetObject(string name)
        {
            return GetControlByName(name);
        }

        /// <summary>
        /// After all children had been initialized
        /// </summary>
        protected override void OnAfterInitialize()
        {
            base.OnAfterInitialize();

            //prepare timeline
            this.Timeline.Initialize();
            this.Timeline.Connect(FindTargetObject);

            //focus the first control
            this.focusedControl = null;
            this.FocusFirstControl();
        }

        /// <summary>
        /// Scene update
        /// </summary>
        protected override void OnUpdate(float elapsedTime)
        {
            base.OnUpdate(elapsedTime);

            // update timeline
            this.Timeline.Update(elapsedTime);

            // manages input
            this.processInput(elapsedTime);

            // deactivate time counting
            if (this.recentlyDeactivated > 0)
            {
                this.recentlyDeactivated -= elapsedTime;
                if (this.recentlyDeactivated < 0) this.recentlyDeactivated = 0;
            }
        }

        #region "Activation"

        private bool active = true;

        /// <summary>
        /// Gets if the scene is active
        /// </summary>
        public bool Active
        {
            get { return this.active; }
        }

        /// <summary>
        /// Activation
        /// </summary>
        public void Activate()
        {
            this.active = true;

            //ignore inputs during a certain amount of time
            this.recentlyActivated = recentActivationDuration;

            this.OnActivate();
        }

        protected virtual void OnActivate() { }

        /// <summary>
        /// Deactivation
        /// </summary>
        public void Deactivate()
        {
            this.active = false;

            //ignore inputs during a certain amount of time
            this.recentlyDeactivated = recentActivationDuration;

            this.OnDeactivate();
        }

        protected virtual void OnDeactivate() { }

        /// <summary>
        /// Gets if the scene was recently deactivated
        /// </summary>
        public bool RecentlyDeactivated
        {
            get { return (this.recentlyDeactivated != 0); }
        }

        /// <summary>
        /// Gets if the scene was recently activated
        /// </summary>
        protected bool RecentlyActivated
        {
            get { return (this.recentlyActivated != 0); }
        }

        #endregion

        #region "Input management"

        /// <summary>
        /// Controls the available player filter options for scene input
        /// </summary>
        public enum PlayerInputFilter
        {
            None,           //no gamepad is processed
            Master,         //only master's player 
            Any,            //all players 
            One,            //only logic player 1
            Two,            //only logic player 2
            Three,          //only logic player 3
            Four            //only logic player 4 
        }

        /// <summary>
        /// This structs defines filters how can produce input events againts this Scene
        /// </summary>
        public struct HandleInputConfig
        {
            [XmlAttribute("enabled")]
            public bool Enabled;

            [XmlAttribute("keyboard")]
            public bool Keyboard;

            [XmlAttribute("player")]
            public PlayerInputFilter Player;
        }

        [XmlElement("Input")]
        public HandleInputConfig InputConfig;

        private float recentlyActivated = 0;

        private float recentlyDeactivated;

        private float recentActivationDuration = 0.20f;

        /// <summary>
        /// Process input
        /// </summary>
        private void processInput(float elapsedTime)
        {
            if (this.InputConfig.Enabled)
            {
                if (this.active)
                {
                    if (!this.RecentlyActivated)
                    {
                        if (this.InputConfig.Keyboard)
                        {
                            processKeyboardInput(elapsedTime);
                        }
                        if (this.InputConfig.Player != PlayerInputFilter.None)
                        {
                            processGamepadsInput(elapsedTime);
                        }
                    }
                    else
                    {
                        this.recentlyActivated -= elapsedTime;
                        if (this.recentlyActivated < 0) this.recentlyActivated = 0;
                    }
                }
                else
                {
                    this.recentlyActivated = recentActivationDuration;
                }
            }
        }

        /// <summary>
        /// This struct controls is a gamepad should be processed for input.
        /// Also stores a reference to the logic players its using the gamepad.
        /// </summary>
        public struct PlayerGamePad
        {
            public bool Active;
            public PlayerIndex GamePadIndex;
            public PlayerManager.Player Player;
        }

        /// <summary>
        /// List of gamepads to be processed
        /// </summary>
        private PlayerGamePad[] gamePadList;

        /// <summary>
        /// Create an array of gamepads to be checked for input
        /// depending of scene player input filter settings
        /// </summary>
        protected void initializeKeypadInput()
        {
            //create the array
            gamePadList = new PlayerGamePad[4];
            for (int i = 0; i < 4; i++)
            {
                gamePadList[i].Active = false;
            }

            PlayerManager.Player outp;

            //asign how many players
            switch (this.InputConfig.Player)
            {
                case PlayerInputFilter.Any:
                    //link to player one
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.One, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = outp;
                        this.gamePadList[0].GamePadIndex = outp.GamepadIndex;
                    }
                    //link to player two
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Two, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[1].Active = true;
                        this.gamePadList[1].Player = outp;
                        this.gamePadList[1].GamePadIndex = outp.GamepadIndex;
                    }
                    //link to player three
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Three, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[2].Active = true;
                        this.gamePadList[2].Player = outp;
                        this.gamePadList[2].GamePadIndex = outp.GamepadIndex;
                    }
                    //link to player four
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Four, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[3].Active = true;
                        this.gamePadList[3].Player = outp;
                        this.gamePadList[3].GamePadIndex = outp.GamepadIndex;
                    }
                    break;
                case PlayerInputFilter.Master:
                    //throw an exception if player is
                    if (Engine.Instance.PlayerManager.DefaultPlayer == null)
                    {
                        Engine.Instance.DebugConsole.Warn("No Master player is defined for scene input!");
                    }
                    else
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = Engine.Instance.PlayerManager.DefaultPlayer;
                        this.gamePadList[0].GamePadIndex = Engine.Instance.PlayerManager.DefaultPlayer.GamepadIndex;
                    }
                    break;
                case PlayerInputFilter.One:
                    //link to player one
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.One, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = outp;
                        this.gamePadList[0].GamePadIndex = outp.GamepadIndex;
                    }
                    break;
                case PlayerInputFilter.Two:
                    //link to player two
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Two, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = outp;
                        this.gamePadList[0].GamePadIndex = outp.GamepadIndex;
                    }
                    break;
                case PlayerInputFilter.Three:
                    //link to player three
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Three, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = outp;
                        this.gamePadList[0].GamePadIndex = outp.GamepadIndex;
                    }
                    break;
                case PlayerInputFilter.Four:
                    //link to player four
                    Engine.Instance.PlayerManager.LogedPlayers.TryGetValue(PlayerIndex.Four, out outp);
                    if (outp != null)
                    {
                        this.gamePadList[0].Active = true;
                        this.gamePadList[0].Player = outp;
                        this.gamePadList[0].GamePadIndex = outp.GamepadIndex;
                    }
                    break;
            }
        }

        /// <summary>
        /// Process all active keypads for input
        /// </summary>
        private void processGamepadsInput(float elapsedTime)
        {
            // process each gamepad this scene cares for
            foreach (PlayerGamePad pad in this.gamePadList)
            {
                // only if active
                if (pad.Active)
                {
                    InputManager iManager = Engine.Instance.InputManager;

                    //
                    // DPAD
                    //

                    // Left on DPAD
                    if (iManager.TestDPadButtonPressed(pad.GamePadIndex, InputManager.THDPadKeys.Left))
                        if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                            this.FocusNavigatePrev(focusedControl);

                    // Right on DPAD
                    if (iManager.TestDPadButtonPressed(pad.GamePadIndex, InputManager.THDPadKeys.Right))
                        if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                            this.FocusNavigateNext(focusedControl);

                    // Down on DPAD
                    if (iManager.TestDPadButtonPressed(pad.GamePadIndex, InputManager.THDPadKeys.Down))
                        if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                            this.FocusNavigateDown(focusedControl);

                    // Up on DPAD
                    if (iManager.TestDPadButtonPressed(pad.GamePadIndex, InputManager.THDPadKeys.Up))
                        if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                            this.FocusNavigateUp(focusedControl);

                    //
                    // THUMB STICK
                    //

                    // Left on LEFT THUMBSTICK
                    if (iManager.TestAnalogStickDigitalDirection(pad.GamePadIndex, InputManager.AnalogSticks.Left, InputManager.AnalogStickDigitalDirections.Left))
                        if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                            this.FocusNavigatePrev(focusedControl);

                    // Right on LEFT THUMBSTICK
                    if (iManager.TestAnalogStickDigitalDirection(pad.GamePadIndex, InputManager.AnalogSticks.Left, InputManager.AnalogStickDigitalDirections.Right))
                        if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                            this.FocusNavigateNext(focusedControl);

                    // Down on LEFT THUMBSTICK
                    if (iManager.TestAnalogStickDigitalDirection(pad.GamePadIndex, InputManager.AnalogSticks.Left, InputManager.AnalogStickDigitalDirections.Down))
                        if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                            this.FocusNavigateDown(focusedControl);

                    // Up on LEFT THUMBSTICK
                    if (iManager.TestAnalogStickDigitalDirection(pad.GamePadIndex, InputManager.AnalogSticks.Left, InputManager.AnalogStickDigitalDirections.Up))
                        if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                            this.FocusNavigateUp(focusedControl);

                    //
                    // Action button
                    //

                    // (A) Button
                    if (iManager.TestButtonPressed(pad.GamePadIndex, InputManager.THGamePadButtons.A))
                        if ((this.focusedControl != null) && (this.focusedControl.CanActivate))
                        {   
                            //raise sound fx
                            this.raiseAudioEvent((int)AudioEvents.ActivableControlActivated, this.focusedControl);

                            //activate the button
                            this.focusedControl.UserActivate();
                        }
                }
            }
        }

        /// <summary>
        /// Process input from keyboard
        /// </summary>
        private void processKeyboardInput(float elapsedTime)
        {
            InputManager iManager = Engine.Instance.InputManager;

            // LEFT KEY
            if (iManager.TestKeyboardPressed(Keys.Left))
                if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                    this.FocusNavigatePrev(focusedControl);

            //RIGHT KEY
            if (iManager.TestKeyboardPressed(Keys.Right))
                if ((focusedControl != null) && (focusedControl.CanNavigateHorizontally))
                    this.FocusNavigateNext(focusedControl);

            //UP KEY
            if (iManager.TestKeyboardPressed(Keys.Up))
                if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                    this.FocusNavigateUp(focusedControl);

            //DOWN KEY
            if (iManager.TestKeyboardPressed(Keys.Down))
                if ((focusedControl != null) && (focusedControl.CanNavigateVertically))
                    this.FocusNavigateDown(focusedControl);

            //ENTER KEY
            if (iManager.TestKeyboardPressed(Keys.Enter))
                if ((focusedControl != null) && (focusedControl.CanActivate))
                {
                    //raise sound fx
                    this.raiseAudioEvent((int)AudioEvents.ActivableControlActivated, this.focusedControl);

                    //activate the button
                    this.focusedControl.UserActivate();
                }

        }

        #endregion

        #region "Focus Management"

        /// <summary>
        /// Stores the control that its currently focused
        /// </summary>
        XUIPlaceableControl focusedControl = null;

        /// <summary>
        /// Focus the first focusable control of the UIScene
        /// </summary>
        private void FocusFirstControl()
        {
            bool found = false;
            foreach (XUIControl control in this.controlDictionary.Values)
            {
                if (!found)
                {
                    XUIPlaceableControl placeable = control as XUIPlaceableControl;
                    if ((placeable != null) && (placeable.CanHaveFocus))
                    {
                        placeable.Focus(false);
                        found = true;
                    }
                }
            }
        }

        /// <summary>
        /// Set a new focused control. It's called from the control itself
        /// </summary>
        internal void SetFocusedControl(XUIPlaceableControl control)
        {
            //unset focus on previously focused control
            if ((focusedControl != null) && (focusedControl != control))
            {
                focusedControl.Unfocus();
            }

            //set the new focused control
            focusedControl = control;

            //raise sound fx
            this.raiseAudioEvent((int)AudioEvents.PlaceableControlGotFocus, control);
        }

        /// <summary>
        /// Focus the next focusable control
        /// </summary>
        internal void FocusNavigateNext(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Right != null) && (sourceControl.FocusReferences.Right != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Right) as XUIPlaceableControl;
            }
            else if ((sourceControl.FocusReferences.Down != null) && (sourceControl.FocusReferences.Down != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Down) as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus(false);

                //raise sound fx
                this.raiseAudioEvent((int)AudioEvents.PlaceableControlNavigateNext, candidateControl);
            }
            else
            {
                Engine.Instance.DebugConsole.Warn(this.UIName +
                    ": Cannot focus to Next control. Maybe it's not defined?");
            }
        }

        /// <summary>
        /// Focus the next focusable control
        /// </summary>
        internal void FocusNavigatePrev(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Left != null) && (sourceControl.FocusReferences.Left != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Left) as XUIPlaceableControl;
            }
            else if ((sourceControl.FocusReferences.Up != null) && (sourceControl.FocusReferences.Up != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Up) as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus(true);

                //raise sound fx
                this.raiseAudioEvent((int)AudioEvents.PlaceableControlNavigatePrevious, candidateControl);
            }
            else
            {
                Engine.Instance.DebugConsole.Warn(this.UIName +
                    ": Cannot focus to Prev control. Maybe it's not defined?");
            }
        }

        /// <summary>
        /// Focus the upside focusable control
        /// </summary>
        internal void FocusNavigateUp(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Up != null) && (sourceControl.FocusReferences.Up != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Up) as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus(true);

                //raise sound fx
                this.raiseAudioEvent((int)AudioEvents.PlaceableControlNavigateUp, candidateControl);
            }
            else
            {
                Engine.Instance.DebugConsole.Warn(this.UIName +
                    ": Cannot focus to Up control. Maybe it's not defined?");
            }
        }

        /// <summary>
        /// Focus the upside focusable control
        /// </summary>
        internal void FocusNavigateDown(XUIPlaceableControl sourceControl)
        {
            //has the currently focused control defined which is the next one?
            XUIPlaceableControl candidateControl = null;
            if ((sourceControl.FocusReferences.Down != null) && (sourceControl.FocusReferences.Down != ""))
            {
                candidateControl = this.GetControlByName(sourceControl.FocusReferences.Down) as XUIPlaceableControl;
            }

            //try focusing the candidate, if it's not focusable, this method will be called again
            if (candidateControl != null)
            {
                candidateControl.Focus(false);

                //raise sound fx
                this.raiseAudioEvent((int)AudioEvents.PlaceableControlNavigateDown, candidateControl);
            }
            else
            {
                Engine.Instance.DebugConsole.Warn(this.UIName +
                    ": Cannot focus to Down control. Maybe it's not defined?");
            }
        }

        #endregion

        #region "Notifications"

        /// <summary>
        /// Handle this event to notify the client code with messages
        /// </summary>
        public event ControlNotification SceneNotification;

        /// <summary>
        /// Notify a message to client code
        /// </summary>
        protected void Notify(String message)
        {
            if (this.SceneNotification != null) this.SceneNotification(message);
        }

        #endregion

        #region "Destruction"

        /// <summary>
        /// Scene destruction
        /// </summary>
        public override void OnBeforeDestroy()
        {
            base.OnBeforeDestroy();

            //destroy the timeline
            this.Timeline.Destroy();

            //destroy all child controls
            foreach (XUIControl cmp in this.controlDictionary.Values)
            {
                cmp.Destroy();
            }
            this.controlDictionary.Clear();

        }

        #endregion

        #region "Exceptions"

        /// <summary>
        /// Raises when a duplicated uiname is used in the same UIScene
        /// </summary>
        public class DuplicatedUIName : UIBaseException
        {
            public DuplicatedUIName(XUIScene scene, XUIControl control)
                : base(Tomahawk.Runtime.Debug.Exceptions.ExceptionSeverity.Error,
                    string.Format("Duplicated UIName '{0}' found on UIScene '{1}'", control.UIName, scene.UIName), null) { }
        }

        #endregion

        #region "Rendering"
        internal void PreRender()
        {
            this.OnPreRender();
        }
        #endregion

        #region "Audio"

        /// <summary>
        /// Default Audio Events available.
        /// </summary>
        public enum AudioEvents
        {
            //Got Focus
            PlaceableControlGotFocus = 0,

            //Lost Focus
            PlaceableControlNavigateNext,
            PlaceableControlNavigatePrevious,
            PlaceableControlNavigateUp,
            PlaceableControlNavigateDown,

            //Activation
            ActivableControlActivated,

            AUDIO_EVENT_MAX
        }

        /// <summary>
        /// Raise an Audio Event
        /// </summary>
        protected void raiseAudioEvent(int audioEventId, XUIControl instigatorControl)
        {
            this.OnAudioEvent(audioEventId, instigatorControl);
        }

        /// <summary>
        /// Override this in client code to handle sound fx events
        /// </summary>
        protected virtual void OnAudioEvent(int audioEventId, XUIControl instigatorControl)
        {
        }

        #endregion

    }

}
